#pragma once

#include "matrix.h"

using namespace mat;
using namespace vect;

/*W2C*/
template <typename vScalar, typename mScalar>
void MakeW2L(Matrix_4x4<mScalar> *a_Out,  Vector_3<vScalar> a_Pos,  Vector_3<vScalar> a_Dir,  Vector_3<vScalar> a_Up)
{
    Vector_3<vScalar> upN;
    Vector_3<vScalar> lftN;
    mScalar *output = a_Out->elements;

    lftN = a_Up ^a_Dir;
    lftN.Normalize();

    upN = a_Dir ^ lftN ;
    upN.Normalize();
    a_Dir.Normalize();

    output[ 0] = lftN[0];
    output[ 1] = upN[0];
    output[ 2] = a_Dir[0];
    output[ 3] = 0.0;

    output[ 4] = lftN[1];
    output[ 5] = upN[1];
    output[ 6] = a_Dir[1];
    output[ 7] = 0.0;

    output[ 8] = lftN[2];
    output[ 9] = upN[2];
    output[10] = a_Dir[2];
    output[11] = 0.0;

    
    output[12] = -(lftN * a_Pos);
    output[13] = -(upN * a_Pos);
    output[14] = -(a_Dir * a_Pos);
    output[15] =  1.0;
}

/*C2W*/
template <typename vScalar, typename mScalar>
void MakeL2W(Matrix_4x4<mScalar> *a_Out,  Vector_3<vScalar> a_Pos,  Vector_3<vScalar> a_Dir,  Vector_3<vScalar> a_Up)
{
    Vector_3<vScalar> upN;
    Vector_3<vScalar> lftN;
    mScalar *output = a_Out->elements;

    lftN = a_Up ^ a_Dir;
    lftN.Normalize();

    upN = a_Dir ^ lftN;
    upN.Normalize();
    a_Dir.Normalize();

    output[ 0] = lftN[0];
    output[ 4] = upN[0];
    output[ 8] = a_Dir[0];
    output[ 3] = 0.0;

    output[ 1] = lftN[1];
    output[ 5] = upN[1];
    output[ 9] = a_Dir[1];
    output[ 7] = 0.0;

    output[ 2] = lftN[2];
    output[ 6] = upN[2];
    output[10] = a_Dir[2];
    output[11] = 0.0;


    output[12] = a_Pos.x;
    output[13] = a_Pos.y;
    output[14] = a_Pos.z;
    output[15] = 1.0;
}


template <typename mScalar>
void makeScaleMtx(Matrix_4x4<mScalar> *a_Out, const mScalar a_X, const mScalar a_Y, const mScalar a_Z)
{
    mScalar *output = a_Out->elements;
    output[ 0] = a_X;
    output[ 1] = 0.0;
    output[ 2] = 0.0;
    output[ 3] = 0.0;

    output[ 4] = 0.0;
    output[ 5] = a_Y;
    output[ 6] = 0.0;
    output[ 7] = 0.0;

    output[ 8] = 0.0;
    output[ 9] = 0.0;
    output[10] = a_Z;
    output[11] = 0.0;

    output[12] = 0.0;
    output[13] = 0.0;
    output[14] = 0.0;
    output[15] = 1.0;
}

template <typename mScalar>
void OrthoMtx(Matrix_4x4<mScalar> *a_Out, const Vector_3<mScalar> a_Min, const Vector_3<mScalar> a_Max)
{
    mScalar *output = a_Out->elements;
    output[ 0] = mScalar(2.0)/(a_Max[0]-a_Min[0]);
    output[ 4] = 0.0;
    output[ 8] = 0.0;
    output[12] = -(a_Max[0]+a_Min[0])/(a_Max[0]-a_Min[0]);

    output[ 1] = 0.0;
    output[ 5] = mScalar(2.0)/(a_Max[1]-a_Min[1]);
    output[ 9] = 0.0;
    output[13] = -(a_Max[1]+a_Min[1])/(a_Max[1]-a_Min[1]);

    /*according to z-inverse distribution*/
    output[ 2] = 0.0;
    output[ 6] = 0.0;
    output[10] = -mScalar(2.0)/(a_Max[2]-a_Min[2]);
    output[14] = (a_Max[2]+a_Min[2])/(a_Max[2]-a_Min[2]);

    output[ 3] = 0.0;
    output[ 7] = 0.0;
    output[11] = 0.0;
    output[15] = 1.0;
}

template <typename mScalar>
void PerspectiveRadMtx(Matrix_4x4<mScalar> *a_Out, const mScalar a_Fovy, const mScalar a_Aspect,
                    const mScalar a_NearDis, const mScalar a_FarDis)
{
    mScalar *output = a_Out->elements;
    const mScalar f = com::coTan(a_Fovy/mScalar(2.0));
    const mScalar Q = a_FarDis/(a_FarDis - a_NearDis);

    output[ 0] = f/a_Aspect;
    output[ 4] = 0.0;
    output[ 8] = 0.0;
    output[12] = 0.0;

    output[ 1] = 0.0;
    output[ 5] = f;
    output[ 9] = 0.0;
    output[13] = 0.0;

    output[ 2] = 0.0;
    output[ 6] = 0.0;
    output[10] = Q;
    output[14] = -Q * a_NearDis;

    output[ 3] = 0.;
    output[ 7] = 0.0;
    output[11] = 1.0;
    output[15] = 0.0;
}

template <typename mScalar>
void PerspectiveDegMtx(Matrix_4x4<mScalar> *a_Out, const mScalar a_Fovy, const mScalar a_Aspect,
                    const mScalar a_NearDis, const mScalar a_FarDis)
{
    PerspectiveRadMtx(a_Out, a_Fovy*(mScalar)PI_180, a_Aspect, a_NearDis, a_FarDis);
}